<!DOCTYPE html>
<html>

  <head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">

  <title>雕虫晓技(十) Android超简单气泡效果</title>
  <meta name="description" content="Android 超简单气泡效果，水下气泡上升效果。">
  <meta name="author" content="GcsSloop">
  <meta name="keywords" content="Android, bubble, view, thread">
  <meta name="关键字" content="Android, bubble, view, thread">
  

  <meta name="twitter:card" content="summary">
  <meta name="twitter:title" content="雕虫晓技(十) Android超简单气泡效果">
  <meta name="twitter:description" content="Android 超简单气泡效果，水下气泡上升效果。">
  <meta name="twitter:keywords" content="Android, bubble, view, thread">
  
  <meta property="og:type" content="article">
  <meta property="og:title" content="雕虫晓技(十) Android超简单气泡效果">
  <meta property="og:description" content="Android 超简单气泡效果，水下气泡上升效果。">
  <meta name="og:keywords" content="Android, bubble, view, thread">

  <meta name="theme-color" content="#343434">
  
  <link rel="icon" type="image/png" href="https://raw.githubusercontent.com/GcsSloop/gcssloop.github.io/master/assets/siteinfo/favicon.png" />
  <link href="https://raw.githubusercontent.com/GcsSloop/gcssloop.github.io/master/assets/siteinfo/favicon.png" rel="shortcut icon" type="image/png">
  
  <link href="//netdna.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css" rel="stylesheet">
  <link rel="stylesheet" href="/css/main.css">

  <link rel="canonical" href="http://www.gcssloop.com/gebug/bubble-sample">
  <link rel="alternate" type="application/rss+xml" title="GcsSloop" href="http://www.gcssloop.com/feed.xml">
  
  <meta name="google-site-verification" content="Z_g58PkzRAyBMxkqrcDdWrTBK8oOWM-7rUHauhLNF2E" />
  <meta name="baidu-site-verification" content="kUtTXCKaZs" />
  <meta name="baidu-site-verification" content="6DuDv3aaJX" />
  
  <!--阅读次数统计-->
  <script async src="https://busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"> </script>
  
  <!--Fuck Weixin and Baidu-->
  <meta http-equiv="Cache-Control" content="no-transform">
  <meta http-equiv=”Cache-Control” content=”no-siteapp” />
  <meta name="applicable-device" content="pc,mobile">
  <meta name="HandheldFriendly" content="true"/>

  <!-- Google Ad -->
  <script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
  <script>
    (adsbygoogle = window.adsbygoogle || []).push({
      google_ad_client: "ca-pub-2767831356529421",
      enable_page_level_ads: true
    });
  </script>

</head>


  <body>

    <span class="mobile btn-mobile-menu">
        <i class="fa fa-list btn-mobile-menu__icon"></i>
        <i class="fa fa-angle-up btn-mobile-close__icon hidden"></i>
    </span>
    
    <header class="panel-cover panel-cover--collapsed" style="background-image: url('/assets/siteinfo/background-cover.jpg')">
  <div class="panel-main">

    <div class="panel-main__inner panel-inverted">
    <div class="panel-main__content">

        <a href="/#blog" title="前往 GcsSloop 的主页" class="blog-button"><img src="/assets/siteinfo/avatar.jpg" width="80" alt="GcsSloop logo" class="panel-cover__logo logo" /></a>
        <h1 class="panel-cover__title panel-title"><a href="/#blog" title="link to homepage for GcsSloop" class="blog-button">GcsSloop</a></h1>

        
        <span class="panel-cover__subtitle panel-subtitle">Just do IT later.</span>
        
        <hr class="panel-cover__divider" />
        <p class="panel-cover__description">嗨，我是 GcsSloop，一名来自2.5次元的魔法师，Android自定义View系列文章作者，非著名程序员。</p>
        <hr class="panel-cover__divider panel-cover__divider--secondary" />
        
        
        <p class="panel-cover__description">欢迎来到我的魔法世界!</p>
        
        
        <div class="navigation-wrapper">
          <div>
            <nav class="cover-navigation cover-navigation--primary">
              <ul class="navigation">
                <li class="navigation__item"><a href="/#blog" title="访问博客" class="blog-button">博客</a></li>
                
                  
                    <li class="navigation__item"><a href="https://github.com/GcsSloop" target="_blank" title="GcsSloop's GitHub">GitHub</a></li>
                  
                  
                
                  
                    <li class="navigation__item"><a href="/timeline" title="博客目录">目录</a></li>
                  
                  
                
                  
                    <li class="navigation__item"><a href="https://xiaozhuanlan.com/u/GcsSloop" target="_blank" title="小专栏">专栏</a></li>
                  
                  
                
                  
                    <li class="navigation__item"><a href="/category/customview" title="自定义View教程目录">自定义控件</a></li>
                  
                  
                
                  
                    <li class="navigation__item"><a href="/friends" title="友链">友链</a></li>
                  
                  
                
              </ul>
            </nav>
          </div>
          
          <div><nav class="cover-navigation navigation--social">
  <ul class="navigation">

  
  <!-- Weibo -->
  <li class="navigation__item">
    <a href="http://weibo.com/GcsSloop" title="@GcsSloop 的微博" target="_blank">
      <i class='social fa fa-weibo'></i>
      <span class="label">Weibo</span>
    </a>
  </li>
  

  
  <!-- Github -->
  <li class="navigation__item">
    <a href="https://github.com/GcsSloop" title="@GcsSloop 的 Github" target="_blank">
      <i class='social fa fa-github'></i>
      <span class="label">Github</span>
    </a>
  </li>
  
  
  
  <!-- Twitter -->
  <li class="navigation__item">
    <a href="http://twitter.com/GcsSloop" title="@GcsSloop" target="_blank">
      <i class='social fa fa-twitter'></i>
      <span class="label">Twitter</span>
    </a>
  </li>
  

    

  

  
  <!-- RSS -->
  <li class="navigation__item">
    <a href="/feed.xml" rel="author" title="RSS" target="_blank">
      <i class='social fa fa-rss'></i>
      <span class="label">RSS</span>
    </a>
  </li>
  

  
  <!-- Email -->
  <li class="navigation__item">
    <a href="mailto:GcsSloop@gmail.com" title="发邮件给我">
      <i class='social fa fa-envelope'></i>
      <span class="label">Email</span>
    </a>
  </li>
  

  
  <!-- Copyright -->
  <li class="navigation__item">
    <a href="http://choosealicense.online" title="选择版权"  target="_blank">
      <i class="social fa fa-copyright"></i>
      <span class="label">版权</span>
    </a>
  </li>
  
  
  </ul>
</nav>
</div>
        </div>
      </div>
    </div>
    
    
    <div class="panel-cover--overlay cover-slate"></div>
    
  </div>
</header>


    <div class="content-wrapper">
        <div class="content-wrapper__inner">
            <article class="post-container post-container--single" itemscope itemtype="http://schema.org/BlogPosting">
  <header class="post-header">
    <div class="post-meta" style="font-size:.8em">
      <time datetime="2018-09-09 00:00:00 +0800" itemprop="datePublished" class="post-meta__date date">2018-09-09</time> &#8226; <span class="post-meta__tags tags">Android</span> &#8226; View <span id="busuanzi_value_page_pv"></span> times.
</span>
    </div>
    <h1 class="post-title">雕虫晓技(十) Android超简单气泡效果</h1>
  </header>

  <section class="post">
    <p><a href="http://android.demo.gcssloop.com/BubbleSample.zip">【示例项目：BubbleSample】</a></p>

<p>最近有用到水下气泡上升效果，因此在网上查了一下资料，结果还真找到了，就是这篇文章 <a href="https://blog.csdn.net/krubo1/article/details/50461528">[Android实例] 水下气泡上升界面效果</a>, 不过这篇文章所附带的示例代码是有些问题的，例如View移除后，线程没有正确关闭，锁屏后再打开屏幕，气泡会挤成一团等问题，因此我在它的原理基础上稍为进行了一些调整和修改，解决了这些问题，它可以实现下面这样的效果：</p>

<p><img src="/assets/gebug/10-bubble-sample/bubble.gif" width="300" /></p>

<h2 id="0-基本原理">0. 基本原理</h2>

<p>气泡效果的基本原理非常简单，其实所谓的气泡就是一个个的半透明圆而已，它的基本逻辑如下：</p>

<ol>
  <li>如果当前圆的数量没有超过数量上限，则随机生成半径不同的圆。</li>
  <li>设定这些圆的初始位置。</li>
  <li>随机设定垂直向上平移速度。</li>
  <li>随机设定水平平移速度。</li>
  <li>不断的刷新圆的位置然后绘制。</li>
  <li>将超出显示区域的圆进行移除。</li>
  <li>不断重复。</li>
</ol>

<p>原理可以说非常简单，但是也有一些需要注意的地方，尤其是线程，最容易出问题。</p>

<p>在原始的 demo 中，直接把线程创建和计算逻辑直接放到了 onDraw 里面，而且没有关闭线程，这自然会导致很多问题的发生。没有关闭线程会造成View的内存泄露，而把计算逻辑放在 onDraw 里面则会加大绘制的负担，拖慢刷新速度，在机能较弱的情况下会导致明显卡顿的发生。而解决这些问题的最好办法则是专事专办，将合适的内容放在合适的位置，下面来看一下具体的代码实现。</p>

<h2 id="1-代码实现">1. 代码实现</h2>

<h3 id="11-定义气泡">1.1 定义气泡</h3>

<p>气泡效果我们关心的属性并不多，主要有这几种：半径、坐标、上升速度、水平平移速度。由于我们只在 View 内部使用，因此直接创建一个内部类，然后在内部类中定义这些属性。</p>

<div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="kd">private</span> <span class="kd">class</span> <span class="nc">Bubble</span> <span class="o">{</span>
    <span class="kt">int</span> <span class="n">radius</span><span class="o">;</span>     <span class="c1">// 气泡半径</span>
    <span class="kt">float</span> <span class="n">speedY</span><span class="o">;</span>   <span class="c1">// 上升速度</span>
    <span class="kt">float</span> <span class="n">speedX</span><span class="o">;</span>   <span class="c1">// 平移速度</span>
    <span class="kt">float</span> <span class="n">x</span><span class="o">;</span>        <span class="c1">// 气泡x坐标</span>
    <span class="kt">float</span> <span class="n">y</span><span class="o">;</span>        <span class="c1">// 气泡y坐标</span>
<span class="o">}</span>
</code></pre>
</div>

<h3 id="12-生命周期处理">1.2 生命周期处理</h3>

<p>由于需要用线程来进行计算和控制刷新，就少不了开启和关闭线程，这个自然要符合 View 的生命周期，因此我在 View 被添加到界面上时开启了一个线程用于生成气泡和刷新气泡位置，然后在 View 从界面上移除的时候关闭了这个线程。</p>

<div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="nd">@Override</span>
<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">onAttachedToWindow</span><span class="o">()</span> <span class="o">{</span>
    <span class="kd">super</span><span class="o">.</span><span class="na">onAttachedToWindow</span><span class="o">();</span>
    <span class="n">startBubbleSync</span><span class="o">();</span>
<span class="o">}</span>

<span class="nd">@Override</span>
<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">onDetachedFromWindow</span><span class="o">()</span> <span class="o">{</span>
    <span class="kd">super</span><span class="o">.</span><span class="na">onDetachedFromWindow</span><span class="o">();</span>
    <span class="n">stopBubbleSync</span><span class="o">();</span>
<span class="o">}</span>
</code></pre>
</div>

<h3 id="13-开启线程">1.3 开启线程</h3>

<p>开启线程非常简单，就是简单的创建了一个线程，然后在里面添加了一个 while 死循环，然后不停的执行 休眠、创建气泡、刷新气泡位置、申请更新UI 等操作。</p>

<blockquote>
  <p>这里没有用变量来控制循环，而是监听了中断事件，在当拦截到 InterruptedException 的时候，使用 break 跳出了死循环，因此线程也就结束了，方法简单粗暴。</p>
</blockquote>

<div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="c1">// 开始气泡线程</span>
<span class="kd">private</span> <span class="kt">void</span> <span class="nf">startBubbleSync</span><span class="o">()</span> <span class="o">{</span>
    <span class="n">stopBubbleSync</span><span class="o">();</span>
    <span class="n">mBubbleThread</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Thread</span><span class="o">()</span> <span class="o">{</span>
        <span class="kd">public</span> <span class="kt">void</span> <span class="nf">run</span><span class="o">()</span> <span class="o">{</span>
            <span class="k">while</span> <span class="o">(</span><span class="kc">true</span><span class="o">)</span> <span class="o">{</span>
                <span class="k">try</span> <span class="o">{</span>
                    <span class="n">Thread</span><span class="o">.</span><span class="na">sleep</span><span class="o">(</span><span class="n">mBubbleRefreshTime</span><span class="o">);</span>
                    <span class="n">tryCreateBubble</span><span class="o">();</span>
                    <span class="n">refreshBubbles</span><span class="o">();</span>
                    <span class="n">postInvalidate</span><span class="o">();</span>
                <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">InterruptedException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
                    <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Bubble线程结束"</span><span class="o">);</span>
                    <span class="k">break</span><span class="o">;</span>
                <span class="o">}</span>
            <span class="o">}</span>
        <span class="o">}</span>
    <span class="o">};</span>
    <span class="n">mBubbleThread</span><span class="o">.</span><span class="na">start</span><span class="o">();</span>
<span class="o">}</span>
</code></pre>
</div>

<h3 id="14-关闭线程">1.4 关闭线程</h3>

<p>由于线程运行时监听了 interrupt 中断，这里直接使用 interrupt 通知线程中断就可以了。</p>

<div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="c1">// 停止气泡线程</span>
<span class="kd">private</span> <span class="kt">void</span> <span class="nf">stopBubbleSync</span><span class="o">()</span> <span class="o">{</span>
    <span class="k">if</span> <span class="o">(</span><span class="kc">null</span> <span class="o">==</span> <span class="n">mBubbleThread</span><span class="o">)</span> <span class="k">return</span><span class="o">;</span>
    <span class="n">mBubbleThread</span><span class="o">.</span><span class="na">interrupt</span><span class="o">();</span>
    <span class="n">mBubbleThread</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
<span class="o">}</span>
</code></pre>
</div>

<h3 id="15-创建气泡">1.5 创建气泡</h3>

<p>为了防止气泡数量过多而占用太多的性能，因此在创建气泡之前需要先判断当前已经有多少个气泡，如果已经有足够多的气泡了，则不再创建新的气泡。</p>

<p>同时，为了让气泡产生过程看起来更合理，在气泡数量没有达到上限之前，会随机的创建气泡，以防止气泡扎堆出现，因此设立了一个随机项，生成的随机数大于 0.95 的时候才生成气泡，让气泡生成过程慢一些。</p>

<p>创建气泡的过程也很简单，就是随机的在设定范围内生成一些属性，然后放到 List 中而已。</p>

<blockquote>
  <p>PS：这里使用了一些硬编码和魔数，属于不太好的习惯。不过由于应用场景固定，这些参数需要调整的概率比较小，影响也不大。</p>
</blockquote>

<div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="c1">// 尝试创建气泡</span>
<span class="kd">private</span> <span class="kt">void</span> <span class="nf">tryCreateBubble</span><span class="o">()</span> <span class="o">{</span>
    <span class="k">if</span> <span class="o">(</span><span class="kc">null</span> <span class="o">==</span> <span class="n">mContentRectF</span><span class="o">)</span> <span class="k">return</span><span class="o">;</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">mBubbles</span><span class="o">.</span><span class="na">size</span><span class="o">()</span> <span class="o">&gt;=</span> <span class="n">mBubbleMaxSize</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">return</span><span class="o">;</span>
    <span class="o">}</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">random</span><span class="o">.</span><span class="na">nextFloat</span><span class="o">()</span> <span class="o">&lt;</span> <span class="mf">0.95</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">return</span><span class="o">;</span>
    <span class="o">}</span>
    <span class="n">Bubble</span> <span class="n">bubble</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Bubble</span><span class="o">();</span>
    <span class="kt">int</span> <span class="n">radius</span> <span class="o">=</span> <span class="n">random</span><span class="o">.</span><span class="na">nextInt</span><span class="o">(</span><span class="n">mBubbleMaxRadius</span> <span class="o">-</span> <span class="n">mBubbleMinRadius</span><span class="o">);</span>
    <span class="n">radius</span> <span class="o">+=</span> <span class="n">mBubbleMinRadius</span><span class="o">;</span>
    <span class="kt">float</span> <span class="n">speedY</span> <span class="o">=</span> <span class="n">random</span><span class="o">.</span><span class="na">nextFloat</span><span class="o">()</span> <span class="o">*</span> <span class="n">mBubbleMaxSpeedY</span><span class="o">;</span>
    <span class="k">while</span> <span class="o">(</span><span class="n">speedY</span> <span class="o">&lt;</span> <span class="mi">1</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">speedY</span> <span class="o">=</span> <span class="n">random</span><span class="o">.</span><span class="na">nextFloat</span><span class="o">()</span> <span class="o">*</span> <span class="n">mBubbleMaxSpeedY</span><span class="o">;</span>
    <span class="o">}</span>
    <span class="n">bubble</span><span class="o">.</span><span class="na">radius</span> <span class="o">=</span> <span class="n">radius</span><span class="o">;</span>
    <span class="n">bubble</span><span class="o">.</span><span class="na">speedY</span> <span class="o">=</span> <span class="n">speedY</span><span class="o">;</span>
    <span class="n">bubble</span><span class="o">.</span><span class="na">x</span> <span class="o">=</span> <span class="n">mWaterRectF</span><span class="o">.</span><span class="na">centerX</span><span class="o">();</span>
    <span class="n">bubble</span><span class="o">.</span><span class="na">y</span> <span class="o">=</span> <span class="n">mWaterRectF</span><span class="o">.</span><span class="na">bottom</span> <span class="o">-</span> <span class="n">radius</span> <span class="o">-</span> <span class="n">mBottleBorder</span> <span class="o">/</span> <span class="mi">2</span><span class="o">;</span>
    <span class="kt">float</span> <span class="n">speedX</span> <span class="o">=</span> <span class="n">random</span><span class="o">.</span><span class="na">nextFloat</span><span class="o">()</span> <span class="o">-</span> <span class="mf">0.5f</span><span class="o">;</span>
    <span class="k">while</span> <span class="o">(</span><span class="n">speedX</span> <span class="o">==</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">speedX</span> <span class="o">=</span> <span class="n">random</span><span class="o">.</span><span class="na">nextFloat</span><span class="o">()</span> <span class="o">-</span> <span class="mf">0.5f</span><span class="o">;</span>
    <span class="o">}</span>
    <span class="n">bubble</span><span class="o">.</span><span class="na">speedX</span> <span class="o">=</span> <span class="n">speedX</span> <span class="o">*</span> <span class="mi">2</span><span class="o">;</span>
    <span class="n">mBubbles</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">bubble</span><span class="o">);</span>
<span class="o">}</span>
</code></pre>
</div>

<h3 id="16-刷新气泡位置">1.6 刷新气泡位置</h3>

<p>这里主要做了两项工作：</p>

<ol>
  <li>将超出显示区域的气泡进行移除。</li>
  <li>计算新的气泡显示位置。</li>
</ol>

<p>可以看到这里没有直接使用原始的List，而是复制了一个 List 进行遍历，这样做主要是为了规避 <code class="highlighter-rouge">ConcurrentModificationException</code> 异常，(对Vector、ArrayList在迭代的时候如果同时对其进行修改就会抛出 java.util.ConcurrentModificationException 异常)。</p>

<p>对复制的 List 进行遍历，然后对超出显示区域的 Bubble 进行移除，对没有超出显示区域的 Bubble 位置进行了刷新。可以看到，这里逻辑比较复杂，有各种加减计算，是为了解决气泡飘到边缘的问题，防止气泡飘出水所在的范围。</p>

<div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="c1">// 刷新气泡位置，对于超出区域的气泡进行移除</span>
<span class="kd">private</span> <span class="kt">void</span> <span class="nf">refreshBubbles</span><span class="o">()</span> <span class="o">{</span>
    <span class="n">List</span><span class="o">&lt;</span><span class="n">Bubble</span><span class="o">&gt;</span> <span class="n">list</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ArrayList</span><span class="o">&lt;&gt;(</span><span class="n">mBubbles</span><span class="o">);</span>
    <span class="k">for</span> <span class="o">(</span><span class="n">Bubble</span> <span class="n">bubble</span> <span class="o">:</span> <span class="n">list</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">bubble</span><span class="o">.</span><span class="na">y</span> <span class="o">-</span> <span class="n">bubble</span><span class="o">.</span><span class="na">speedY</span> <span class="o">&lt;=</span> <span class="n">mWaterRectF</span><span class="o">.</span><span class="na">top</span> <span class="o">+</span> <span class="n">bubble</span><span class="o">.</span><span class="na">radius</span><span class="o">)</span> <span class="o">{</span>
            <span class="n">mBubbles</span><span class="o">.</span><span class="na">remove</span><span class="o">(</span><span class="n">bubble</span><span class="o">);</span>
        <span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
            <span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="n">mBubbles</span><span class="o">.</span><span class="na">indexOf</span><span class="o">(</span><span class="n">bubble</span><span class="o">);</span>
            <span class="k">if</span> <span class="o">(</span><span class="n">bubble</span><span class="o">.</span><span class="na">x</span> <span class="o">+</span> <span class="n">bubble</span><span class="o">.</span><span class="na">speedX</span> <span class="o">&lt;=</span> <span class="n">mWaterRectF</span><span class="o">.</span><span class="na">left</span> <span class="o">+</span> <span class="n">bubble</span><span class="o">.</span><span class="na">radius</span> <span class="o">+</span> <span class="n">mBottleBorder</span> <span class="o">/</span> <span class="mi">2</span><span class="o">)</span> <span class="o">{</span>
                <span class="n">bubble</span><span class="o">.</span><span class="na">x</span> <span class="o">=</span> <span class="n">mWaterRectF</span><span class="o">.</span><span class="na">left</span> <span class="o">+</span> <span class="n">bubble</span><span class="o">.</span><span class="na">radius</span> <span class="o">+</span> <span class="n">mBottleBorder</span> <span class="o">/</span> <span class="mi">2</span><span class="o">;</span>
            <span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">bubble</span><span class="o">.</span><span class="na">x</span> <span class="o">+</span> <span class="n">bubble</span><span class="o">.</span><span class="na">speedX</span> <span class="o">&gt;=</span> <span class="n">mWaterRectF</span><span class="o">.</span><span class="na">right</span> <span class="o">-</span> <span class="n">bubble</span><span class="o">.</span><span class="na">radius</span> <span class="o">-</span> <span class="n">mBottleBorder</span> <span class="o">/</span> <span class="mi">2</span><span class="o">)</span> <span class="o">{</span>
                <span class="n">bubble</span><span class="o">.</span><span class="na">x</span> <span class="o">=</span> <span class="n">mWaterRectF</span><span class="o">.</span><span class="na">right</span> <span class="o">-</span> <span class="n">bubble</span><span class="o">.</span><span class="na">radius</span> <span class="o">-</span> <span class="n">mBottleBorder</span> <span class="o">/</span> <span class="mi">2</span><span class="o">;</span>
            <span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
                <span class="n">bubble</span><span class="o">.</span><span class="na">x</span> <span class="o">=</span> <span class="n">bubble</span><span class="o">.</span><span class="na">x</span> <span class="o">+</span> <span class="n">bubble</span><span class="o">.</span><span class="na">speedX</span><span class="o">;</span>
            <span class="o">}</span>
            <span class="n">bubble</span><span class="o">.</span><span class="na">y</span> <span class="o">=</span> <span class="n">bubble</span><span class="o">.</span><span class="na">y</span> <span class="o">-</span> <span class="n">bubble</span><span class="o">.</span><span class="na">speedY</span><span class="o">;</span>
            <span class="n">mBubbles</span><span class="o">.</span><span class="na">set</span><span class="o">(</span><span class="n">i</span><span class="o">,</span> <span class="n">bubble</span><span class="o">);</span>
        <span class="o">}</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre>
</div>

<h3 id="17-绘制气泡">1.7 绘制气泡</h3>

<p>绘制气泡同样简单，就是遍历 List，然后画圆就行了。</p>

<p>这里同样复制了一个新的 List 进行操作，不过这个与上面的原因不同，是为了防止多线程问题。由于在绘制的过程中，我们的计算线程可能会对原始 List 进行更新，可能导致异常的发生。为了避免这样的问题，就复制了一个 List 出来用于遍历绘制。</p>

<div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="c1">// 绘制气泡</span>
<span class="kd">private</span> <span class="kt">void</span> <span class="nf">drawBubble</span><span class="o">(</span><span class="n">Canvas</span> <span class="n">canvas</span><span class="o">)</span> <span class="o">{</span>
    <span class="n">List</span><span class="o">&lt;</span><span class="n">Bubble</span><span class="o">&gt;</span> <span class="n">list</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ArrayList</span><span class="o">&lt;&gt;(</span><span class="n">mBubbles</span><span class="o">);</span>
    <span class="k">for</span> <span class="o">(</span><span class="n">Bubble</span> <span class="n">bubble</span> <span class="o">:</span> <span class="n">list</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">if</span> <span class="o">(</span><span class="kc">null</span> <span class="o">==</span> <span class="n">bubble</span><span class="o">)</span> <span class="k">continue</span><span class="o">;</span>
        <span class="n">canvas</span><span class="o">.</span><span class="na">drawCircle</span><span class="o">(</span><span class="n">bubble</span><span class="o">.</span><span class="na">x</span><span class="o">,</span> <span class="n">bubble</span><span class="o">.</span><span class="na">y</span><span class="o">,</span>
                <span class="n">bubble</span><span class="o">.</span><span class="na">radius</span><span class="o">,</span> <span class="n">mBubblePaint</span><span class="o">);</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre>
</div>

<h2 id="2-完整代码">2. 完整代码</h2>

<p>完整的示例代码非常简单，所以直接贴在了正文中，同时，你也可以从文末下载完整的项目代码。</p>

<div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">BubbleView</span> <span class="kd">extends</span> <span class="n">View</span> <span class="o">{</span>

    <span class="kd">private</span> <span class="kt">int</span> <span class="n">mBubbleMaxRadius</span> <span class="o">=</span> <span class="mi">30</span><span class="o">;</span>          <span class="c1">// 气泡最大半径 px</span>
    <span class="kd">private</span> <span class="kt">int</span> <span class="n">mBubbleMinRadius</span> <span class="o">=</span> <span class="mi">5</span><span class="o">;</span>           <span class="c1">// 气泡最小半径 px</span>
    <span class="kd">private</span> <span class="kt">int</span> <span class="n">mBubbleMaxSize</span> <span class="o">=</span> <span class="mi">30</span><span class="o">;</span>            <span class="c1">// 气泡数量</span>
    <span class="kd">private</span> <span class="kt">int</span> <span class="n">mBubbleRefreshTime</span> <span class="o">=</span> <span class="mi">20</span><span class="o">;</span>        <span class="c1">// 刷新间隔</span>
    <span class="kd">private</span> <span class="kt">int</span> <span class="n">mBubbleMaxSpeedY</span> <span class="o">=</span> <span class="mi">5</span><span class="o">;</span>           <span class="c1">// 气泡速度</span>
    <span class="kd">private</span> <span class="kt">int</span> <span class="n">mBubbleAlpha</span> <span class="o">=</span> <span class="mi">128</span><span class="o">;</span>             <span class="c1">// 气泡画笔</span>

    <span class="kd">private</span> <span class="kt">float</span> <span class="n">mBottleWidth</span><span class="o">;</span>                 <span class="c1">// 瓶子宽度</span>
    <span class="kd">private</span> <span class="kt">float</span> <span class="n">mBottleHeight</span><span class="o">;</span>                <span class="c1">// 瓶子高度</span>
    <span class="kd">private</span> <span class="kt">float</span> <span class="n">mBottleRadius</span><span class="o">;</span>                <span class="c1">// 瓶子底部转角半径</span>
    <span class="kd">private</span> <span class="kt">float</span> <span class="n">mBottleBorder</span><span class="o">;</span>                <span class="c1">// 瓶子边缘宽度</span>
    <span class="kd">private</span> <span class="kt">float</span> <span class="n">mBottleCapRadius</span><span class="o">;</span>             <span class="c1">// 瓶子顶部转角半径</span>
    <span class="kd">private</span> <span class="kt">float</span> <span class="n">mWaterHeight</span><span class="o">;</span>                 <span class="c1">// 水的高度</span>

    <span class="kd">private</span> <span class="n">RectF</span> <span class="n">mContentRectF</span><span class="o">;</span>                <span class="c1">// 实际可用内容区域</span>
    <span class="kd">private</span> <span class="n">RectF</span> <span class="n">mWaterRectF</span><span class="o">;</span>                  <span class="c1">// 水占用的区域</span>

    <span class="kd">private</span> <span class="n">Path</span> <span class="n">mBottlePath</span><span class="o">;</span>                   <span class="c1">// 外部瓶子</span>
    <span class="kd">private</span> <span class="n">Path</span> <span class="n">mWaterPath</span><span class="o">;</span>                    <span class="c1">// 水</span>

    <span class="kd">private</span> <span class="n">Paint</span> <span class="n">mBottlePaint</span><span class="o">;</span>                 <span class="c1">// 瓶子画笔</span>
    <span class="kd">private</span> <span class="n">Paint</span> <span class="n">mWaterPaint</span><span class="o">;</span>                  <span class="c1">// 水画笔</span>
    <span class="kd">private</span> <span class="n">Paint</span> <span class="n">mBubblePaint</span><span class="o">;</span>                 <span class="c1">// 气泡画笔</span>

    <span class="kd">public</span> <span class="nf">BubbleView</span><span class="o">(</span><span class="n">Context</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">this</span><span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="kc">null</span><span class="o">);</span>
    <span class="o">}</span>

    <span class="kd">public</span> <span class="nf">BubbleView</span><span class="o">(</span><span class="n">Context</span> <span class="n">context</span><span class="o">,</span> <span class="nd">@Nullable</span> <span class="n">AttributeSet</span> <span class="n">attrs</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">this</span><span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="n">attrs</span><span class="o">,</span> <span class="mi">0</span><span class="o">);</span>
    <span class="o">}</span>

    <span class="kd">public</span> <span class="nf">BubbleView</span><span class="o">(</span><span class="n">Context</span> <span class="n">context</span><span class="o">,</span> <span class="nd">@Nullable</span> <span class="n">AttributeSet</span> <span class="n">attrs</span><span class="o">,</span> <span class="kt">int</span> <span class="n">defStyleAttr</span><span class="o">)</span> <span class="o">{</span>
        <span class="kd">super</span><span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="n">attrs</span><span class="o">,</span> <span class="n">defStyleAttr</span><span class="o">);</span>
        <span class="n">mWaterRectF</span> <span class="o">=</span> <span class="k">new</span> <span class="n">RectF</span><span class="o">();</span>

        <span class="n">mBottleWidth</span> <span class="o">=</span> <span class="n">dp2px</span><span class="o">(</span><span class="mi">130</span><span class="o">);</span>
        <span class="n">mBottleHeight</span> <span class="o">=</span> <span class="n">dp2px</span><span class="o">(</span><span class="mi">260</span><span class="o">);</span>
        <span class="n">mBottleBorder</span> <span class="o">=</span> <span class="n">dp2px</span><span class="o">(</span><span class="mi">8</span><span class="o">);</span>
        <span class="n">mBottleRadius</span> <span class="o">=</span> <span class="n">dp2px</span><span class="o">(</span><span class="mi">15</span><span class="o">);</span>
        <span class="n">mBottleCapRadius</span> <span class="o">=</span> <span class="n">dp2px</span><span class="o">(</span><span class="mi">5</span><span class="o">);</span>

        <span class="n">mWaterHeight</span> <span class="o">=</span> <span class="n">dp2px</span><span class="o">(</span><span class="mi">240</span><span class="o">);</span>

        <span class="n">mBottlePath</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Path</span><span class="o">();</span>
        <span class="n">mWaterPath</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Path</span><span class="o">();</span>

        <span class="n">mBottlePaint</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Paint</span><span class="o">();</span>
        <span class="n">mBottlePaint</span><span class="o">.</span><span class="na">setAntiAlias</span><span class="o">(</span><span class="kc">true</span><span class="o">);</span>
        <span class="n">mBottlePaint</span><span class="o">.</span><span class="na">setStyle</span><span class="o">(</span><span class="n">Paint</span><span class="o">.</span><span class="na">Style</span><span class="o">.</span><span class="na">STROKE</span><span class="o">);</span>
        <span class="n">mBottlePaint</span><span class="o">.</span><span class="na">setStrokeCap</span><span class="o">(</span><span class="n">Paint</span><span class="o">.</span><span class="na">Cap</span><span class="o">.</span><span class="na">ROUND</span><span class="o">);</span>
        <span class="n">mBottlePaint</span><span class="o">.</span><span class="na">setColor</span><span class="o">(</span><span class="n">Color</span><span class="o">.</span><span class="na">WHITE</span><span class="o">);</span>
        <span class="n">mBottlePaint</span><span class="o">.</span><span class="na">setStrokeWidth</span><span class="o">(</span><span class="n">mBottleBorder</span><span class="o">);</span>

        <span class="n">mWaterPaint</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Paint</span><span class="o">();</span>
        <span class="n">mWaterPaint</span><span class="o">.</span><span class="na">setAntiAlias</span><span class="o">(</span><span class="kc">true</span><span class="o">);</span>

        <span class="n">initBubble</span><span class="o">();</span>
    <span class="o">}</span>

    <span class="nd">@Override</span>
    <span class="kd">protected</span> <span class="kt">void</span> <span class="nf">onSizeChanged</span><span class="o">(</span><span class="kt">int</span> <span class="n">w</span><span class="o">,</span> <span class="kt">int</span> <span class="n">h</span><span class="o">,</span> <span class="kt">int</span> <span class="n">oldw</span><span class="o">,</span> <span class="kt">int</span> <span class="n">oldh</span><span class="o">)</span> <span class="o">{</span>
        <span class="kd">super</span><span class="o">.</span><span class="na">onSizeChanged</span><span class="o">(</span><span class="n">w</span><span class="o">,</span> <span class="n">h</span><span class="o">,</span> <span class="n">oldw</span><span class="o">,</span> <span class="n">oldh</span><span class="o">);</span>

        <span class="n">mContentRectF</span> <span class="o">=</span> <span class="k">new</span> <span class="n">RectF</span><span class="o">(</span><span class="n">getPaddingLeft</span><span class="o">(),</span> <span class="n">getPaddingTop</span><span class="o">(),</span> <span class="n">w</span> <span class="o">-</span> <span class="n">getPaddingRight</span><span class="o">(),</span> <span class="n">h</span> <span class="o">-</span> <span class="n">getPaddingBottom</span><span class="o">());</span>

        <span class="kt">float</span> <span class="n">bl</span> <span class="o">=</span> <span class="n">mContentRectF</span><span class="o">.</span><span class="na">centerX</span><span class="o">()</span> <span class="o">-</span> <span class="n">mBottleWidth</span> <span class="o">/</span> <span class="mi">2</span><span class="o">;</span>
        <span class="kt">float</span> <span class="n">bt</span> <span class="o">=</span> <span class="n">mContentRectF</span><span class="o">.</span><span class="na">centerY</span><span class="o">()</span> <span class="o">-</span> <span class="n">mBottleHeight</span> <span class="o">/</span> <span class="mi">2</span><span class="o">;</span>
        <span class="kt">float</span> <span class="n">br</span> <span class="o">=</span> <span class="n">mContentRectF</span><span class="o">.</span><span class="na">centerX</span><span class="o">()</span> <span class="o">+</span> <span class="n">mBottleWidth</span> <span class="o">/</span> <span class="mi">2</span><span class="o">;</span>
        <span class="kt">float</span> <span class="n">bb</span> <span class="o">=</span> <span class="n">mContentRectF</span><span class="o">.</span><span class="na">centerY</span><span class="o">()</span> <span class="o">+</span> <span class="n">mBottleHeight</span> <span class="o">/</span> <span class="mi">2</span><span class="o">;</span>
        <span class="n">mBottlePath</span><span class="o">.</span><span class="na">reset</span><span class="o">();</span>
        <span class="n">mBottlePath</span><span class="o">.</span><span class="na">moveTo</span><span class="o">(</span><span class="n">bl</span> <span class="o">-</span> <span class="n">mBottleCapRadius</span><span class="o">,</span> <span class="n">bt</span> <span class="o">-</span> <span class="n">mBottleCapRadius</span><span class="o">);</span>
        <span class="n">mBottlePath</span><span class="o">.</span><span class="na">quadTo</span><span class="o">(</span><span class="n">bl</span><span class="o">,</span> <span class="n">bt</span> <span class="o">-</span> <span class="n">mBottleCapRadius</span><span class="o">,</span> <span class="n">bl</span><span class="o">,</span> <span class="n">bt</span><span class="o">);</span>
        <span class="n">mBottlePath</span><span class="o">.</span><span class="na">lineTo</span><span class="o">(</span><span class="n">bl</span><span class="o">,</span> <span class="n">bb</span> <span class="o">-</span> <span class="n">mBottleRadius</span><span class="o">);</span>
        <span class="n">mBottlePath</span><span class="o">.</span><span class="na">quadTo</span><span class="o">(</span><span class="n">bl</span><span class="o">,</span> <span class="n">bb</span><span class="o">,</span> <span class="n">bl</span> <span class="o">+</span> <span class="n">mBottleRadius</span><span class="o">,</span> <span class="n">bb</span><span class="o">);</span>
        <span class="n">mBottlePath</span><span class="o">.</span><span class="na">lineTo</span><span class="o">(</span><span class="n">br</span> <span class="o">-</span> <span class="n">mBottleRadius</span><span class="o">,</span> <span class="n">bb</span><span class="o">);</span>
        <span class="n">mBottlePath</span><span class="o">.</span><span class="na">quadTo</span><span class="o">(</span><span class="n">br</span><span class="o">,</span> <span class="n">bb</span><span class="o">,</span> <span class="n">br</span><span class="o">,</span> <span class="n">bb</span> <span class="o">-</span> <span class="n">mBottleRadius</span><span class="o">);</span>
        <span class="n">mBottlePath</span><span class="o">.</span><span class="na">lineTo</span><span class="o">(</span><span class="n">br</span><span class="o">,</span> <span class="n">bt</span><span class="o">);</span>
        <span class="n">mBottlePath</span><span class="o">.</span><span class="na">quadTo</span><span class="o">(</span><span class="n">br</span><span class="o">,</span> <span class="n">bt</span> <span class="o">-</span> <span class="n">mBottleCapRadius</span><span class="o">,</span> <span class="n">br</span> <span class="o">+</span> <span class="n">mBottleCapRadius</span><span class="o">,</span> <span class="n">bt</span> <span class="o">-</span> <span class="n">mBottleCapRadius</span><span class="o">);</span>


        <span class="n">mWaterPath</span><span class="o">.</span><span class="na">reset</span><span class="o">();</span>
        <span class="n">mWaterPath</span><span class="o">.</span><span class="na">moveTo</span><span class="o">(</span><span class="n">bl</span><span class="o">,</span> <span class="n">bb</span> <span class="o">-</span> <span class="n">mWaterHeight</span><span class="o">);</span>
        <span class="n">mWaterPath</span><span class="o">.</span><span class="na">lineTo</span><span class="o">(</span><span class="n">bl</span><span class="o">,</span> <span class="n">bb</span> <span class="o">-</span> <span class="n">mBottleRadius</span><span class="o">);</span>
        <span class="n">mWaterPath</span><span class="o">.</span><span class="na">quadTo</span><span class="o">(</span><span class="n">bl</span><span class="o">,</span> <span class="n">bb</span><span class="o">,</span> <span class="n">bl</span> <span class="o">+</span> <span class="n">mBottleRadius</span><span class="o">,</span> <span class="n">bb</span><span class="o">);</span>
        <span class="n">mWaterPath</span><span class="o">.</span><span class="na">lineTo</span><span class="o">(</span><span class="n">br</span> <span class="o">-</span> <span class="n">mBottleRadius</span><span class="o">,</span> <span class="n">bb</span><span class="o">);</span>
        <span class="n">mWaterPath</span><span class="o">.</span><span class="na">quadTo</span><span class="o">(</span><span class="n">br</span><span class="o">,</span> <span class="n">bb</span><span class="o">,</span> <span class="n">br</span><span class="o">,</span> <span class="n">bb</span> <span class="o">-</span> <span class="n">mBottleRadius</span><span class="o">);</span>
        <span class="n">mWaterPath</span><span class="o">.</span><span class="na">lineTo</span><span class="o">(</span><span class="n">br</span><span class="o">,</span> <span class="n">bb</span> <span class="o">-</span> <span class="n">mWaterHeight</span><span class="o">);</span>
        <span class="n">mWaterPath</span><span class="o">.</span><span class="na">close</span><span class="o">();</span>

        <span class="n">mWaterRectF</span><span class="o">.</span><span class="na">set</span><span class="o">(</span><span class="n">bl</span><span class="o">,</span> <span class="n">bb</span> <span class="o">-</span> <span class="n">mWaterHeight</span><span class="o">,</span> <span class="n">br</span><span class="o">,</span> <span class="n">bb</span><span class="o">);</span>

        <span class="n">LinearGradient</span> <span class="n">gradient</span> <span class="o">=</span> <span class="k">new</span> <span class="n">LinearGradient</span><span class="o">(</span><span class="n">mWaterRectF</span><span class="o">.</span><span class="na">centerX</span><span class="o">(),</span> <span class="n">mWaterRectF</span><span class="o">.</span><span class="na">top</span><span class="o">,</span>
                <span class="n">mWaterRectF</span><span class="o">.</span><span class="na">centerX</span><span class="o">(),</span> <span class="n">mWaterRectF</span><span class="o">.</span><span class="na">bottom</span><span class="o">,</span> <span class="mi">0</span><span class="n">xFF4286f4</span><span class="o">,</span> <span class="mi">0</span><span class="n">xFF373B44</span><span class="o">,</span> <span class="n">Shader</span><span class="o">.</span><span class="na">TileMode</span><span class="o">.</span><span class="na">CLAMP</span><span class="o">);</span>
        <span class="n">mWaterPaint</span><span class="o">.</span><span class="na">setShader</span><span class="o">(</span><span class="n">gradient</span><span class="o">);</span>
    <span class="o">}</span>

    <span class="nd">@Override</span>
    <span class="kd">protected</span> <span class="kt">void</span> <span class="nf">onDraw</span><span class="o">(</span><span class="n">Canvas</span> <span class="n">canvas</span><span class="o">)</span> <span class="o">{</span>
        <span class="kd">super</span><span class="o">.</span><span class="na">onDraw</span><span class="o">(</span><span class="n">canvas</span><span class="o">);</span>
        <span class="n">canvas</span><span class="o">.</span><span class="na">drawPath</span><span class="o">(</span><span class="n">mWaterPath</span><span class="o">,</span> <span class="n">mWaterPaint</span><span class="o">);</span>
        <span class="n">canvas</span><span class="o">.</span><span class="na">drawPath</span><span class="o">(</span><span class="n">mBottlePath</span><span class="o">,</span> <span class="n">mBottlePaint</span><span class="o">);</span>
        <span class="n">drawBubble</span><span class="o">(</span><span class="n">canvas</span><span class="o">);</span>
    <span class="o">}</span>

    <span class="c1">//--- 气泡效果 ---------------------------------------------------------------------------------</span>

    <span class="nd">@Override</span>
    <span class="kd">protected</span> <span class="kt">void</span> <span class="nf">onAttachedToWindow</span><span class="o">()</span> <span class="o">{</span>
        <span class="kd">super</span><span class="o">.</span><span class="na">onAttachedToWindow</span><span class="o">();</span>
        <span class="n">startBubbleSync</span><span class="o">();</span>
    <span class="o">}</span>

    <span class="nd">@Override</span>
    <span class="kd">protected</span> <span class="kt">void</span> <span class="nf">onDetachedFromWindow</span><span class="o">()</span> <span class="o">{</span>
        <span class="kd">super</span><span class="o">.</span><span class="na">onDetachedFromWindow</span><span class="o">();</span>
        <span class="n">stopBubbleSync</span><span class="o">();</span>
    <span class="o">}</span>


    <span class="kd">private</span> <span class="kd">class</span> <span class="nc">Bubble</span> <span class="o">{</span>
        <span class="kt">int</span> <span class="n">radius</span><span class="o">;</span>     <span class="c1">// 气泡半径</span>
        <span class="kt">float</span> <span class="n">speedY</span><span class="o">;</span>   <span class="c1">// 上升速度</span>
        <span class="kt">float</span> <span class="n">speedX</span><span class="o">;</span>   <span class="c1">// 平移速度</span>
        <span class="kt">float</span> <span class="n">x</span><span class="o">;</span>        <span class="c1">// 气泡x坐标</span>
        <span class="kt">float</span> <span class="n">y</span><span class="o">;</span>        <span class="c1">// 气泡y坐标</span>
    <span class="o">}</span>

    <span class="kd">private</span> <span class="n">ArrayList</span><span class="o">&lt;</span><span class="n">Bubble</span><span class="o">&gt;</span> <span class="n">mBubbles</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ArrayList</span><span class="o">&lt;&gt;();</span>

    <span class="kd">private</span> <span class="n">Random</span> <span class="n">random</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Random</span><span class="o">();</span>
    <span class="kd">private</span> <span class="n">Thread</span> <span class="n">mBubbleThread</span><span class="o">;</span>

    <span class="c1">// 初始化气泡</span>
    <span class="kd">private</span> <span class="kt">void</span> <span class="nf">initBubble</span><span class="o">()</span> <span class="o">{</span>
        <span class="n">mBubblePaint</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Paint</span><span class="o">();</span>
        <span class="n">mBubblePaint</span><span class="o">.</span><span class="na">setColor</span><span class="o">(</span><span class="n">Color</span><span class="o">.</span><span class="na">WHITE</span><span class="o">);</span>
        <span class="n">mBubblePaint</span><span class="o">.</span><span class="na">setAlpha</span><span class="o">(</span><span class="n">mBubbleAlpha</span><span class="o">);</span>
    <span class="o">}</span>

    <span class="c1">// 开始气泡线程</span>
    <span class="kd">private</span> <span class="kt">void</span> <span class="nf">startBubbleSync</span><span class="o">()</span> <span class="o">{</span>
        <span class="n">stopBubbleSync</span><span class="o">();</span>
        <span class="n">mBubbleThread</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Thread</span><span class="o">()</span> <span class="o">{</span>
            <span class="kd">public</span> <span class="kt">void</span> <span class="nf">run</span><span class="o">()</span> <span class="o">{</span>
                <span class="k">while</span> <span class="o">(</span><span class="kc">true</span><span class="o">)</span> <span class="o">{</span>
                    <span class="k">try</span> <span class="o">{</span>
                        <span class="n">Thread</span><span class="o">.</span><span class="na">sleep</span><span class="o">(</span><span class="n">mBubbleRefreshTime</span><span class="o">);</span>
                        <span class="n">tryCreateBubble</span><span class="o">();</span>
                        <span class="n">refreshBubbles</span><span class="o">();</span>
                        <span class="n">postInvalidate</span><span class="o">();</span>
                    <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">InterruptedException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
                        <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Bubble线程结束"</span><span class="o">);</span>
                        <span class="k">break</span><span class="o">;</span>
                    <span class="o">}</span>
                <span class="o">}</span>
            <span class="o">}</span>
        <span class="o">};</span>
        <span class="n">mBubbleThread</span><span class="o">.</span><span class="na">start</span><span class="o">();</span>
    <span class="o">}</span>

    <span class="c1">// 停止气泡线程</span>
    <span class="kd">private</span> <span class="kt">void</span> <span class="nf">stopBubbleSync</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">if</span> <span class="o">(</span><span class="kc">null</span> <span class="o">==</span> <span class="n">mBubbleThread</span><span class="o">)</span> <span class="k">return</span><span class="o">;</span>
        <span class="n">mBubbleThread</span><span class="o">.</span><span class="na">interrupt</span><span class="o">();</span>
        <span class="n">mBubbleThread</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
    <span class="o">}</span>

    <span class="c1">// 绘制气泡</span>
    <span class="kd">private</span> <span class="kt">void</span> <span class="nf">drawBubble</span><span class="o">(</span><span class="n">Canvas</span> <span class="n">canvas</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">List</span><span class="o">&lt;</span><span class="n">Bubble</span><span class="o">&gt;</span> <span class="n">list</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ArrayList</span><span class="o">&lt;&gt;(</span><span class="n">mBubbles</span><span class="o">);</span>
        <span class="k">for</span> <span class="o">(</span><span class="n">Bubble</span> <span class="n">bubble</span> <span class="o">:</span> <span class="n">list</span><span class="o">)</span> <span class="o">{</span>
            <span class="k">if</span> <span class="o">(</span><span class="kc">null</span> <span class="o">==</span> <span class="n">bubble</span><span class="o">)</span> <span class="k">continue</span><span class="o">;</span>
            <span class="n">canvas</span><span class="o">.</span><span class="na">drawCircle</span><span class="o">(</span><span class="n">bubble</span><span class="o">.</span><span class="na">x</span><span class="o">,</span> <span class="n">bubble</span><span class="o">.</span><span class="na">y</span><span class="o">,</span>
                    <span class="n">bubble</span><span class="o">.</span><span class="na">radius</span><span class="o">,</span> <span class="n">mBubblePaint</span><span class="o">);</span>
        <span class="o">}</span>
    <span class="o">}</span>

    <span class="c1">// 尝试创建气泡</span>
    <span class="kd">private</span> <span class="kt">void</span> <span class="nf">tryCreateBubble</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">if</span> <span class="o">(</span><span class="kc">null</span> <span class="o">==</span> <span class="n">mContentRectF</span><span class="o">)</span> <span class="k">return</span><span class="o">;</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">mBubbles</span><span class="o">.</span><span class="na">size</span><span class="o">()</span> <span class="o">&gt;=</span> <span class="n">mBubbleMaxSize</span><span class="o">)</span> <span class="o">{</span>
            <span class="k">return</span><span class="o">;</span>
        <span class="o">}</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">random</span><span class="o">.</span><span class="na">nextFloat</span><span class="o">()</span> <span class="o">&lt;</span> <span class="mf">0.95</span><span class="o">)</span> <span class="o">{</span>
            <span class="k">return</span><span class="o">;</span>
        <span class="o">}</span>
        <span class="n">Bubble</span> <span class="n">bubble</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Bubble</span><span class="o">();</span>
        <span class="kt">int</span> <span class="n">radius</span> <span class="o">=</span> <span class="n">random</span><span class="o">.</span><span class="na">nextInt</span><span class="o">(</span><span class="n">mBubbleMaxRadius</span> <span class="o">-</span> <span class="n">mBubbleMinRadius</span><span class="o">);</span>
        <span class="n">radius</span> <span class="o">+=</span> <span class="n">mBubbleMinRadius</span><span class="o">;</span>
        <span class="kt">float</span> <span class="n">speedY</span> <span class="o">=</span> <span class="n">random</span><span class="o">.</span><span class="na">nextFloat</span><span class="o">()</span> <span class="o">*</span> <span class="n">mBubbleMaxSpeedY</span><span class="o">;</span>
        <span class="k">while</span> <span class="o">(</span><span class="n">speedY</span> <span class="o">&lt;</span> <span class="mi">1</span><span class="o">)</span> <span class="o">{</span>
            <span class="n">speedY</span> <span class="o">=</span> <span class="n">random</span><span class="o">.</span><span class="na">nextFloat</span><span class="o">()</span> <span class="o">*</span> <span class="n">mBubbleMaxSpeedY</span><span class="o">;</span>
        <span class="o">}</span>
        <span class="n">bubble</span><span class="o">.</span><span class="na">radius</span> <span class="o">=</span> <span class="n">radius</span><span class="o">;</span>
        <span class="n">bubble</span><span class="o">.</span><span class="na">speedY</span> <span class="o">=</span> <span class="n">speedY</span><span class="o">;</span>
        <span class="n">bubble</span><span class="o">.</span><span class="na">x</span> <span class="o">=</span> <span class="n">mWaterRectF</span><span class="o">.</span><span class="na">centerX</span><span class="o">();</span>
        <span class="n">bubble</span><span class="o">.</span><span class="na">y</span> <span class="o">=</span> <span class="n">mWaterRectF</span><span class="o">.</span><span class="na">bottom</span> <span class="o">-</span> <span class="n">radius</span> <span class="o">-</span> <span class="n">mBottleBorder</span> <span class="o">/</span> <span class="mi">2</span><span class="o">;</span>
        <span class="kt">float</span> <span class="n">speedX</span> <span class="o">=</span> <span class="n">random</span><span class="o">.</span><span class="na">nextFloat</span><span class="o">()</span> <span class="o">-</span> <span class="mf">0.5f</span><span class="o">;</span>
        <span class="k">while</span> <span class="o">(</span><span class="n">speedX</span> <span class="o">==</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
            <span class="n">speedX</span> <span class="o">=</span> <span class="n">random</span><span class="o">.</span><span class="na">nextFloat</span><span class="o">()</span> <span class="o">-</span> <span class="mf">0.5f</span><span class="o">;</span>
        <span class="o">}</span>
        <span class="n">bubble</span><span class="o">.</span><span class="na">speedX</span> <span class="o">=</span> <span class="n">speedX</span> <span class="o">*</span> <span class="mi">2</span><span class="o">;</span>
        <span class="n">mBubbles</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">bubble</span><span class="o">);</span>
    <span class="o">}</span>

    <span class="c1">// 刷新气泡位置，对于超出区域的气泡进行移除</span>
    <span class="kd">private</span> <span class="kt">void</span> <span class="nf">refreshBubbles</span><span class="o">()</span> <span class="o">{</span>
        <span class="n">List</span><span class="o">&lt;</span><span class="n">Bubble</span><span class="o">&gt;</span> <span class="n">list</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ArrayList</span><span class="o">&lt;&gt;(</span><span class="n">mBubbles</span><span class="o">);</span>
        <span class="k">for</span> <span class="o">(</span><span class="n">Bubble</span> <span class="n">bubble</span> <span class="o">:</span> <span class="n">list</span><span class="o">)</span> <span class="o">{</span>
            <span class="k">if</span> <span class="o">(</span><span class="n">bubble</span><span class="o">.</span><span class="na">y</span> <span class="o">-</span> <span class="n">bubble</span><span class="o">.</span><span class="na">speedY</span> <span class="o">&lt;=</span> <span class="n">mWaterRectF</span><span class="o">.</span><span class="na">top</span> <span class="o">+</span> <span class="n">bubble</span><span class="o">.</span><span class="na">radius</span><span class="o">)</span> <span class="o">{</span>
                <span class="n">mBubbles</span><span class="o">.</span><span class="na">remove</span><span class="o">(</span><span class="n">bubble</span><span class="o">);</span>
            <span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
                <span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="n">mBubbles</span><span class="o">.</span><span class="na">indexOf</span><span class="o">(</span><span class="n">bubble</span><span class="o">);</span>
                <span class="k">if</span> <span class="o">(</span><span class="n">bubble</span><span class="o">.</span><span class="na">x</span> <span class="o">+</span> <span class="n">bubble</span><span class="o">.</span><span class="na">speedX</span> <span class="o">&lt;=</span> <span class="n">mWaterRectF</span><span class="o">.</span><span class="na">left</span> <span class="o">+</span> <span class="n">bubble</span><span class="o">.</span><span class="na">radius</span> <span class="o">+</span> <span class="n">mBottleBorder</span> <span class="o">/</span> <span class="mi">2</span><span class="o">)</span> <span class="o">{</span>
                    <span class="n">bubble</span><span class="o">.</span><span class="na">x</span> <span class="o">=</span> <span class="n">mWaterRectF</span><span class="o">.</span><span class="na">left</span> <span class="o">+</span> <span class="n">bubble</span><span class="o">.</span><span class="na">radius</span> <span class="o">+</span> <span class="n">mBottleBorder</span> <span class="o">/</span> <span class="mi">2</span><span class="o">;</span>
                <span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">bubble</span><span class="o">.</span><span class="na">x</span> <span class="o">+</span> <span class="n">bubble</span><span class="o">.</span><span class="na">speedX</span> <span class="o">&gt;=</span> <span class="n">mWaterRectF</span><span class="o">.</span><span class="na">right</span> <span class="o">-</span> <span class="n">bubble</span><span class="o">.</span><span class="na">radius</span> <span class="o">-</span> <span class="n">mBottleBorder</span> <span class="o">/</span> <span class="mi">2</span><span class="o">)</span> <span class="o">{</span>
                    <span class="n">bubble</span><span class="o">.</span><span class="na">x</span> <span class="o">=</span> <span class="n">mWaterRectF</span><span class="o">.</span><span class="na">right</span> <span class="o">-</span> <span class="n">bubble</span><span class="o">.</span><span class="na">radius</span> <span class="o">-</span> <span class="n">mBottleBorder</span> <span class="o">/</span> <span class="mi">2</span><span class="o">;</span>
                <span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
                    <span class="n">bubble</span><span class="o">.</span><span class="na">x</span> <span class="o">=</span> <span class="n">bubble</span><span class="o">.</span><span class="na">x</span> <span class="o">+</span> <span class="n">bubble</span><span class="o">.</span><span class="na">speedX</span><span class="o">;</span>
                <span class="o">}</span>
                <span class="n">bubble</span><span class="o">.</span><span class="na">y</span> <span class="o">=</span> <span class="n">bubble</span><span class="o">.</span><span class="na">y</span> <span class="o">-</span> <span class="n">bubble</span><span class="o">.</span><span class="na">speedY</span><span class="o">;</span>
                <span class="n">mBubbles</span><span class="o">.</span><span class="na">set</span><span class="o">(</span><span class="n">i</span><span class="o">,</span> <span class="n">bubble</span><span class="o">);</span>
            <span class="o">}</span>
        <span class="o">}</span>
    <span class="o">}</span>

    <span class="kd">private</span> <span class="kt">float</span> <span class="nf">dp2px</span><span class="o">(</span><span class="kt">float</span> <span class="n">dpValue</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">return</span> <span class="n">TypedValue</span><span class="o">.</span><span class="na">applyDimension</span><span class="o">(</span><span class="n">TypedValue</span><span class="o">.</span><span class="na">COMPLEX_UNIT_DIP</span><span class="o">,</span> <span class="n">dpValue</span><span class="o">,</span> <span class="n">getResources</span><span class="o">().</span><span class="na">getDisplayMetrics</span><span class="o">());</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre>
</div>

<h2 id="3-结语">3. 结语</h2>

<p>由于本项目是一个示例性质的项目，因此设计的比较简单，结构也是简单粗暴，并没有经过精心的雕琢，存在一些疏漏也说不定，如果大家觉得逻辑上存在问题或者有什么疑惑，欢迎在下面(公众号、小专栏)的评论区留言。</p>

<p><strong>公众号查看到该文章的可以通过点击【阅读原文】下载到所需的示例代码，非公众号阅读的可以从文末或者文初下载到示例项目。</strong></p>

<p><a href="http://android.demo.gcssloop.com/BubbleSample.zip">【示例项目：BubbleSample】</a></p>


    <hr>
  </section>
</article>

<!--广告-->

<!--
<div>
<a href="https://m.aliyun.com/act/team1111/?spm=5176.11533457.1089570.5.424777e3AF8WOJ&userCode=hn5smxtw#/" target="_blank"><img src="/assets/aliyun/1111-980-120.jpg" width="100%"></a>
</div>
-->
<!--捐赠晶石-->
<section class="contribute">
    <script type="text/javascript"> 
      function toggle() {
        var con = document.getElementById("contribute");
        if (con.style.display == "none") {
          con.style.display = "block";
        } else {
          con.style.display = "none";
        }
      }
    </script> 
    <blockquote style="background-color:#F5F5F5; padding: 10px 20px 20px 10px; margin:0px" >
      <h4> 如果你觉得我的文章对你有帮助的话，欢迎赞助一些服务器费用! </h4>
      <p></p>
      <a id=“btn-toggle-contribute” class="btn-contribute" onclick="toggle()" >¥ 点击赞助</a>
      <br>
      <div id="contribute" style="display:none;">
        <p align="center" >
        <img src="/assets/images/wechat.png" alt="微信">
        <img src="/assets/images/alipay.png" alt="支付宝">
        </p>
        <p align="left" >
          <b>感谢所有支持我的魔法师，所有支持过我的魔法师都可以通过微信(GcsSloop)联系我，获赠我的付费专栏！</b>
          <!--
          <a href="/contribute">点击这里查看捐赠者名单。</a>
          -->
        </p>
      </div>
    </blockquote>
</section>
<div>
  <h2>欢迎关注我的微信公众号</h2>
  <img src="/assets/images/banner.jpg" width="100%">
</div>

<!--阅读更多-->
<section class="read-more">
  
  
  <div class="read-more-item">
    <span class="read-more-item-dim">最近的文章</span>
    <h2 class="post-list__post-title post-title"><a href="/customview/paint-base" title="link to 安卓自定义View进阶-画笔基础(Paint)">安卓自定义View进阶-画笔基础(Paint)</a></h2>
    <p class="excerpt">在Android自定义View系列文章中，前面的部分有详细的讲解画布(Canvas)的功能和用法，但是和画布(Canvas)共同出现的画笔(Paint)却没有详细的讲解，本文带大家较为详细的了解...&hellip;</p>
    <div class="post-list__meta">
      <time datetime="2018-10-24 00:00:00 +0800" class="post-list__meta--date date">2018-10-24</time> &#8226; <span class="post-list__meta--tags tags">CustomView</span>
      <br/><br/>
      <a style="float:none; margin:0 auto;" class="btn-border-small" href=/customview/paint-base>继续阅读</a></div>
   </div>
   
   
   
   
   <div class="read-more-item">
       <span class="read-more-item-dim">更早的文章</span>
       <h2 class="post-list__post-title post-title"><a href="/tools/arc-seekbar" title="link to ArcSeekBar(圆弧拖动条)">ArcSeekBar(圆弧拖动条)</a></h2>
       <p class="excerpt">GitHub项目地址: https://github.com/GcsSloop/arc-seekbar这个是在公司项目中准备使用的一个控件，本准备直接在网上搜一个用，可惜找到的很多都是 Prog...&hellip;</p>
       <div class="post-list__meta">
          <time datetime="2018-09-02 00:00:00 +0800" class="post-list__meta--date date">2018-09-02</time> &#8226; <span class="post-list__meta--tags tags">Tools</span>
          <br/><br/>
          <a style="float:none; margin:0 auto;" class="btn-border-small" href=/tools/arc-seekbar>继续阅读</a>
       </div>
   </div>
   
</section>

<!--网易云跟帖-->
<!--
<div id="cloud-tie-wrapper" class="cloud-tie-wrapper"></div>
<script src="https://img1.cache.netease.com/f2e/tie/yun/sdk/loader.js"></script>
<script>
var cloudTieConfig = {
  url: document.location.href, 
  sourceId: "",
  productKey: "a85dba2840134721a7b69a15b2e0f217",
  target: "cloud-tie-wrapper"
};
var yunManualLoad = true;
Tie.loader("aHR0cHM6Ly9hcGkuZ2VudGllLjE2My5jb20vcGMvbGl2ZXNjcmlwdC5odG1s", true);
</script>
-->

<style type="text/css">
.isso-comment > div.avatar {
    border: 0px;
    box-shadow: none;
    display: block;
    float: left;
    width: 7%;
    margin: 3px 15px 0 0;
}
.isso-postbox > .form-wrapper > .auth-section .post-action > input {
    border-radius: 6px;
    padding: 6px;
    padding-left: 16px;
    padding-right: 16px;
    border: 1px solid #CCC;
    background-color: #D58D44;
    cursor: pointer;
    outline: 0;
    color: #fff;
    size: 10;
    line-height: 1.4em;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
.isso-postbox > .form-wrapper > .auth-section .post-action > input:hover {
    background-color: #272822;
}
.isso-postbox > .form-wrapper > .auth-section .post-action > input:active {
    background-color: #986530;
}
</style>

<section id="isso-thread"></section>

<script data-isso="//47.52.58.34:1234/"
        data-isso-css="true"
        data-isso-lang="zh"
        data-isso-reply-to-self="false"
        data-isso-require-author="false"
        data-isso-require-email="false"
        data-isso-max-comments-top="10"
        data-isso-max-comments-nested="5"
        data-isso-reveal-on-click="5"
        data-isso-avatar="true"
        data-isso-avatar-bg="#f0f0f0"
        data-isso-avatar-fg="#9abf88 #5698c4 #e279a3 #9163b6 ..."
        data-isso-vote="true"
        data-vote-levels=""
        src="//47.52.58.34:1234/js/embed.min.js">
        </script>

<!--
<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
-->
<!-- OneV's Den -->
<!--
<ins class="adsbygoogle"
     style="display:block"
     data-ad-client="ca-pub-3324997515191619"
     data-ad-slot="9170309685"
     data-ad-format="auto"></ins>
<script>
(adsbygoogle = window.adsbygoogle || []).push({});
</script>
-->

            <section class="footer">
    <footer>
    	<span class="footer__copyright">本站点采用<a rel="license" href="https://creativecommons.org/licenses/by-nc-nd/4.0/deed.zh" target="_blank">知识共享 署名-非商业性使用-禁止演绎 4.0 国际 许可协议</a></span>
        <span class="footer__copyright">本站由 <a href="http://www.GcsSloop.com">@GcsSloop</a> 创建，采用 <a href="https://github.com/GcsSloop/Gcs-Vno-Jekyll" target="_blank">Gcs-Vno-Jekyll</a> 作为主题。<span id="busuanzi_container_site_pv"> 总访问量 <span id="busuanzi_value_site_pv"></span> 次</span> - &copy; 2019</span>
        <span class="footer__sitemap, footer__copyright"><a href="http://www.gcssloop.com/sitemap.xml" target="_blank">Site Map</a>
        <a href="http://www.gcssloop.com/vip" target="_blank">vip</a></span>
    </footer>
</section>

        </div>
    </div>
    
    <script type="text/javascript" src="//code.jquery.com/jquery-1.11.3.min.js"></script>

<script type="text/javascript" src="/js/highlight.pack.js"></script>
<script>hljs.initHighlightingOnLoad();</script>

<script type="text/javascript" src="/js/main.js"></script>

<script>
  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
  (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
  m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
  })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');

  ga('create', 'UA-82493667-1', 'auto');
  ga('send', 'pageview');

</script>

    
  </body>

</html>
